package com.hmdp.utils;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.entity.RedisData;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
@Component
public class RedisUtils {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    private static final Integer MAX_RETRIES = 5;
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);    public void set(String key, Object value, Long time, TimeUnit timeUnit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, timeUnit);
    }
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit timeUnit){
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }
    public <R,ID> R
    queryById(ID id, String keyPrefix, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit timeUnit) {
        String cacheStr = stringRedisTemplate.opsForValue().get(keyPrefix + id);
        if (cacheStr!=null){
            R object = JSONUtil.toBean(cacheStr,type);
            return object;
        }
        R newCache = dbFallback.apply(id);
        log.info("查询了一次数据库");
        if (newCache==null) {
            stringRedisTemplate.opsForValue().set(keyPrefix + id,
                    JSONUtil.toJsonStr(""),timeUnit.toSeconds(time));
            return newCache;
        }
        return newCache;
    }
    public <R,ID> R cacheThrough(ID id,String keyPrefix, Class<R> type, Function<ID,R>  dbFallback, Long time, TimeUnit timeUnit) {
        String cacheStr = stringRedisTemplate.opsForValue().get(keyPrefix + id);
        if (cacheStr!=null){
            R object = JSONUtil.toBean(cacheStr, type);
            return object;
        }
        String lock = keyPrefix + id;
        Boolean status = stringRedisTemplate.opsForValue().setIfAbsent(lock, "1", time, timeUnit);
        try {
            int counter = 0;
            while (!status && counter<MAX_RETRIES){
                // 正在等待
                Thread.sleep(100);
                status = stringRedisTemplate.opsForValue().setIfAbsent(lock, "1", time, timeUnit);
                counter++;
            }
            if (status){
                //竞争到锁后，查看是否存在缓存
                String afterCache = stringRedisTemplate.opsForValue().get(keyPrefix + id);
                if (afterCache!=null){
                    R newCache = JSONUtil.toBean(afterCache, type);
                    return newCache;
                }
                //不存在,查询数据库
                R cache = dbFallback.apply(id);
                log.info("查询了一次数据库");
                // 不存在，缓存空
                if (cache==null) {
                    stringRedisTemplate.opsForValue().set(keyPrefix + id,
                            JSONUtil.toJsonStr(""), time, timeUnit);
                    return cache;
                }
                //存在，写入缓存
                stringRedisTemplate.opsForValue()
                        .set(keyPrefix + id, JSONUtil.toJsonStr(cache), time, timeUnit);
                return cache;
            }
            return null;
        } catch (InterruptedException e){
            e.printStackTrace();
            return null;
        }finally {
            // 释放锁
            stringRedisTemplate.delete(lock);
        }
    }
    public <R, ID> R cacheRebuild(ID id,
                                  String keyPrefix,
                                  String lockPrefix,
                                  Long expireTime,
                                  TimeUnit timeUnit,
                                  Class<R> type,
                                  Function<ID, R> dbFallback) {
        // 1. 构造缓存Key
        String key = keyPrefix + id;
        String cacheStr = stringRedisTemplate.opsForValue().get(key);

        // 2. 解析缓存数据
        if (cacheStr != null) {
            JSONObject json = JSONUtil.parseObj(cacheStr);
            LocalDateTime cacheExpire = json.get("expireTime", LocalDateTime.class);
            R data = json.get("data", type);

            // 3. 检查缓存有效性
            if (data != null && cacheExpire.isAfter(LocalDateTime.now())) {
                return data;
            }

            // 4. 获取分布式锁
            String lockKey = lockPrefix + id;
            boolean locked = stringRedisTemplate.opsForValue()
                    .setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);

            if (locked) {
                try {
                    // 5. 双重检查
                    String newCacheStr = stringRedisTemplate.opsForValue().get(key);
                    JSONObject newJson = JSONUtil.parseObj(newCacheStr);
                    LocalDateTime newExpire = newJson.get("expireTime", LocalDateTime.class);

                    if (newExpire != null && newExpire.isAfter(LocalDateTime.now())) {
                        return newJson.get("data", type);
                    }

                    // 6. 异步重建缓存
                    CACHE_REBUILD_EXECUTOR.submit(() -> {
                        try {
                            R newData = dbFallback.apply(id);
                            RedisData<R> redisData = new RedisData<>(
                                    LocalDateTime.now().plusSeconds(timeUnit.toSeconds(expireTime)),
                                    newData
                            );
                            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
                        } catch (Exception e) {
                            log.error("缓存重建失败", e);
                        } finally {
                            stringRedisTemplate.delete(lockKey);
                        }
                    });
                } catch (Exception e) {
                    log.error("缓存处理异常", e);
                    stringRedisTemplate.delete(lockKey);
                }
            }
            return data;
        }
        return null;
    }
}
